package com.gitee.luosl.cola.util;

import com.gitee.luosl.cola.lang.function.checked.CheckedFunction;
import com.gitee.luosl.cola.lang.function.checked.CheckedPredicate;
import com.gitee.luosl.cola.lang.function.checked.CheckedRunnable;
import com.gitee.luosl.cola.lang.function.checked.CheckedSupplier;

import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public interface Try<T> {

    boolean isSuccess();

    boolean isFailure();

    T get();

    default T getOrElse(Function<? super Throwable, ? extends T> other) {
        Objects.requireNonNull(other, "other is null");
        if (isFailure()) {
            return other.apply(getCause());
        } else {
            return get();
        }
    }

    default T getOrElse(Supplier<? extends T> supplier) {
        if (isFailure()) {
            return supplier.get();
        } else {
            return get();
        }
    }

    default T getOrElse(T value) {
        if (isFailure()) {
            return value;
        } else {
            return get();
        }
    }

    default void orElseRun(Consumer<? super Throwable> action) {
        Objects.requireNonNull(action, "action is null");
        if (isFailure()) {
            action.accept(getCause());
        }
    }

    default <X extends Throwable> T getOrElseThrow(Function<? super Throwable, X> exceptionProvider) throws X {
        Objects.requireNonNull(exceptionProvider, "exceptionProvider is null");
        if (isFailure()) {
            throw exceptionProvider.apply(getCause());
        } else {
            return get();
        }
    }

    Throwable getCause();

    default <U> Try<U> map(Function<? super T, ? extends U> mapper){
        CheckedFunction<T, U> checkedMapper = mapper::apply;
        return mapTry(checkedMapper);
    }

    default <U> Try<U> mapTry(CheckedFunction<? super T, ? extends U> mapper) {
        if (isFailure()) {
            return (Failure<U>) this;
        } else {
            try {
                return new Success<>(mapper.apply(get()));
            } catch (Throwable t) {
                return new Failure<>(t);
            }
        }
    }

    @SuppressWarnings("unchecked")
    default <U> Try<U> flatMap(Function<? super T, ? extends Try<? extends U>> mapper){
        CheckedFunction<T, Try<U>> checkedMapper = v -> (Try<U>) mapper.apply(v);
        return flatMapTry(checkedMapper);
    }

    @SuppressWarnings("unchecked")
    default <U> Try<U> flatMapTry(CheckedFunction<? super T, ? extends Try<? extends U>> mapper) {
        if (isFailure()) {
            return (Failure<U>) this;
        } else {
            try {
                return (Try<U>) mapper.apply(get());
            } catch (Throwable t) {
                return new Failure<>(t);
            }
        }
    }

    default Try<T> filter(Predicate<? super T> predicate){
        CheckedPredicate<? super T> checkedPredicate = predicate::test;
        return filterTry(checkedPredicate);
    }

    default Try<T> filterTry(CheckedPredicate<? super T> predicate) {
        return filterTry(predicate, NoSuchElementException::new);
    }

    default Try<T> filter(Predicate<? super T> predicate, Supplier<? extends Throwable> supplier){
        CheckedPredicate<? super T> checkedPredicate = predicate::test;
        return filterTry(checkedPredicate, supplier);
    }

    default Try<T> filterTry(CheckedPredicate<? super T> predicate, Supplier<? extends Throwable> supplier) {
        if (isFailure()) {
            return this;
        } else {
            try {
                if (predicate.test(get())) {
                    return this;
                } else {
                    return new Failure<>(supplier.get());
                }
            } catch (Throwable t) {
                return new Failure<>(t);
            }
        }
    }

    default Try<T> onSuccess(Consumer<? super T> consumer){
        if (isSuccess()) {
            consumer.accept(get());
        }
        return this;
    }

    default Try<T> onFailure(Consumer<? super Throwable> action) {
        if (isFailure()) {
            action.accept(getCause());
        }
        return this;
    }

    @SuppressWarnings("unchecked")
    default <E extends Throwable> Try<T> onFailure(Class<E> exceptionType, Consumer<? super Throwable> action){
        if (isFailure() && exceptionType.isAssignableFrom(getCause().getClass())) {
            action.accept((E) getCause());
        }
        return this;
    }

    default Try<T> andFinally(CheckedRunnable runnable) {
        try {
            runnable.run();
            return this;
        } catch (Throwable t) {
            return new Failure<>(t);
        }
    }

    default Optional<T> toOption() {
        if(isSuccess()){
            return Optional.ofNullable(get());
        }else{
            return Optional.empty();
        }
    }

    class Success<T> implements Try<T> {

        private final T value;

        private Success(T value) {
            this.value = value;
        }

        @Override
        public boolean isSuccess() {
            return true;
        }

        @Override
        public boolean isFailure() {
            return false;
        }

        @Override
        public T get() {
            return value;
        }

        @Override
        public Throwable getCause() {
            throw new UnsupportedOperationException("unsupported getCause on Success");
        }

        @Override
        public boolean equals(Object obj) {
            return (obj == this) || (obj instanceof Success && Objects.equals(value, ((Success<?>) obj).value));
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(value);
        }
    }

    class Failure<T> implements Try<T> {

        private final Throwable throwable;

        private Failure(Throwable throwable) {
            this.throwable = throwable;
        }

        @Override
        public boolean isSuccess() {
            return false;
        }

        @Override
        public boolean isFailure() {
            return true;
        }

        @Override
        public T get() {
            throw new UnsupportedOperationException("unsupported get on Failure", getCause());
        }

        @Override
        public Throwable getCause() {
            return throwable;
        }

        @Override
        public boolean equals(Object obj) {
            return (obj == this) || (obj instanceof Failure && getCause().getClass().getName().equals(obj.getClass().getName()));
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(getCause().getClass().getName());
        }
    }

    static <T> Try<T> success(T value){
        return new Success<>(value);
    }

    static <T> Try<T> failure(Throwable throwable){
        return new Failure<>(throwable);
    }

    static <T> Try<T> of(CheckedSupplier<? extends T> supplier) {
        try{
            return new Success<>(supplier.get());
        } catch (Throwable throwable) {
            return new Failure<>(throwable);
        }
    }

    static <T> Try<T> ofSupplier(Supplier<? extends T> supplier) {
        return of(supplier::get);
    }

    static Try<Void> of(CheckedRunnable runnable){
        CheckedSupplier<Void> supplier = () -> {
            runnable.run();
            return null;
        };
        return of(supplier);
    }

    static Try<Void> ofRunnable(Runnable runnable){
        return of(runnable::run);
    }

}
